var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
// Block Elements - HTML 4.01
var block = makeMap("address,applet,blockquote,button,center,dd,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
// Inline Elements - HTML 4.01
var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
// Elements that you can, intentionally, leave open
// (and which close themselves)
var close_self = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
/** Current matching mode */
var cur_mode = 'xhtml';
/** Last matched HTML pair */
var last_match = {
opening_tag: null, // tag() or comment() object
closing_tag: null, // tag() or comment() object
start_ix: -1,
end_ix: -1
};
function setMode(new_mode) {
if (!new_mode || new_mode != 'html')
new_mode = 'xhtml';
cur_mode = new_mode;
}
function tag(match, ix) {
var name = match[1].toLowerCase();
return {
name: name,
full_tag: match[0],
start: ix,
end: ix + match[0].length,
unary: Boolean(match[3]) || (name in empty && cur_mode == 'html'),
has_close: Boolean(match[3]),
type: 'tag',
close_self: (name in close_self && cur_mode == 'html')
};
}
function comment(start, end) {
return {
start: start,
end: end,
type: 'comment'
};
}
function makeMap(str){
var obj = {}, items = str.split(",");
for ( var i = 0; i < items.length; i++ )
obj[ items[i] ] = true;
return obj;
}
/**
* Makes selection ranges for matched tag pair
* @param {tag} opening_tag
* @param {tag} closing_tag
* @param {Number} ix
*/
function makeRange(opening_tag, closing_tag, ix) {
ix = ix || 0;
var start_ix = -1,
end_ix = -1;
if (opening_tag && !closing_tag) { // unary element
start_ix = opening_tag.start;
end_ix = opening_tag.end;
} else if (opening_tag && closing_tag) { // complete element
if (
(opening_tag.start < ix && opening_tag.end > ix) ||
(closing_tag.start <= ix && closing_tag.end > ix)
) {
start_ix = opening_tag.start;
end_ix = closing_tag.end;
} else {
start_ix = opening_tag.end;
end_ix = closing_tag.start;
}
}
return [start_ix, end_ix];
}
/**
* Save matched tag for later use and return found indexes
* @param {tag} opening_tag
* @param {tag} closing_tag
* @param {Number} ix
* @return {Array}
*/
function saveMatch(opening_tag, closing_tag, ix) {
ix = ix || 0;
last_match.opening_tag = opening_tag;
last_match.closing_tag = closing_tag;
var range = makeRange(opening_tag, closing_tag, ix);